<?php
/**
 * Product: proxy_server.
 * Date: 2021-11-12
 * Time: 10:21
 */

namespace ASW\Utility\Buffer;

class WriteableBuffer extends Buffer
{
    public function writeString(string $string): bool
    {
        if (false === $this->writeUChar(strlen($string))) return false;
        $this->write($string);
        return true;
    }

    public function writeUChar(int $value): bool
    {
        return $this->writePack('C', $value);
    }

    private function writePack(string $mode, int|float ...$values): bool
    {
        [$limitMin, $limitMax] = $this->getPackValueLimit($mode);
        if ($limitMin === false || $limitMax === false) {
            $this->setLastError("pack mode $mode unsupported");
            return false;
        }

        foreach ($values as $value) {
            if ($value < $limitMin || $value > $limitMax) {
                $this->setLastError("want write value $value as invalid mode $mode");
                return false;
            }
        }

        $packMode = str_repeat($mode, count($values));
        $bin      = pack($packMode, ...$values);
        $this->write($bin);
        return true;
    }

    private function getPackValueLimit(string $mode): array
    {
        return match ($mode) {
            'c' => [(0xFF + 1) / -2, (0xFF - 1) / 2],
            'C' => [0, 0xFF],
            's' => [(0xFFFF + 1) / -2, (0xFFFF - 1) / 2],
            'S', 'n', 'v' => [0, 0xFFFF],
            'i' => [-2 ** (PHP_INT_SIZE * 8 - 1), (2 ** (PHP_INT_SIZE * 8 - 1)) - 1],
            'I' => [0, (2 ** (PHP_INT_SIZE * 8)) - 1],
            'l' => [(0xFFFFFFFF + 1) / -2, (0xFFFFFFFF - 1) / 2],
            'L', 'N', 'V' => [0, 0xFFFFFFFF],
            'q' => [(0xFFFFFFFFFFFFFFFF + 1) / -2, (0xFFFFFFFFFFFFFFFF - 1) / 2],
            'Q', 'J', 'P' => [0, 0xFFFFFFFFFFFFFFFF],
            'f', 'g', 'G', 'd', 'e', 'E' => [PHP_FLOAT_MIN, PHP_FLOAT_MAX],
            default => [false, false],
        };
    }

    public function write(string $string)
    {
        $this->_content = "{$this->_content}$string";
        $this->moveToEnd();
    }

    public function writeChar(int $value): bool
    {
        return $this->writePack('c', $value);
    }

    public function writeInt16(int $value): bool
    {
        return $this->writePack('s', $value);
    }

    public function writeUInt16(int $value): bool
    {
        return $this->writePack('S', $value);
    }

    public function writeUInt16BE(int $value): bool
    {
        return $this->writePack('n', $value);
    }

    public function writeUInt16LE(int $value): bool
    {
        return $this->writePack('v', $value);
    }

    public function writeInt(int $value): bool
    {
        return $this->writePack('i', $value);
    }

    public function writeUInt(int $value): bool
    {
        return $this->writePack('I', $value);
    }

    public function writeInt32(int $value): bool
    {
        return $this->writePack('l', $value);
    }

    public function writeUInt32(int $value): bool
    {
        return $this->writePack('L', $value);
    }

    public function writeUInt32BE(int $value): bool
    {
        return $this->writePack('N', $value);
    }

    public function writeUInt32LE(int $value): bool
    {
        return $this->writePack('V', $value);
    }

    public function writeInt64(int $value): bool
    {
        return $this->writePack('q', $value);
    }

    public function writeUInt64(int $value): bool
    {
        return $this->writePack('Q', $value);
    }

    public function writeUInt64BE(int $value): bool
    {
        return $this->writePack('J', $value);
    }

    public function writeUInt64LE(int $value): bool
    {
        return $this->writePack('P', $value);
    }

    public function writeFloat(float $value): bool
    {
        return $this->writePack('f', $value);
    }

    public function writeFloatBE(float $value): bool
    {
        return $this->writePack('G', $value);
    }

    public function writeFloatLE(float $value): bool
    {
        return $this->writePack('g', $value);
    }

    public function writeDouble(float $value): bool
    {
        return $this->writePack('d', $value);
    }

    public function writeDoubleBE(float $value): bool
    {
        return $this->writePack('E', $value);
    }

    public function writeDoubleLE(float $value): bool
    {
        return $this->writePack('e', $value);
    }
}